use quantsp2p::hello;

use std::{thread, time::Duration};

use log::{error, info};
use quantsp2p::{config::P2pConfig, Client, EventHandler};
use tokio::task;

use clap::Parser;

/// Test Program for quantsp2p
#[derive(Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
struct Args {
    /// Secret of this p2p-node
    #[arg(short, long)]
    pub secret: Option<String>,

    /// Multiaddr of boot node
    #[arg(short, long)]
    pub bootnode: Option<String>,

    /// Name of this node
    #[arg(short, long)]
    pub name: String,

    /// Number of times to greet
    #[arg(short, long, default_value_t = 1)]
    pub count: u8,
}

#[tokio::main]
async fn main() {
    pretty_env_logger::init();

    hello();

    let args = Args::parse();

    info!("Hello Args= {:?}!", args);

    let mut config = P2pConfig {
        secret: args.secret.clone(),
        addr: "/ip4/0.0.0.0/tcp/0".to_string(),
        pubsub_topics: vec!["block".to_string(), "tx".to_string()],
        ..Default::default()
    };
    config.pubsub_topics = vec!["block".to_string(), "tx".to_string()];

    if let Some(ref addr) = args.bootnode {
        config.boot_node = addr.parse().ok();
    }

    info!("bootnode={:?} config={:?}", args.bootnode, config);

    let (client, mut server) = quantsp2p::new(config).unwrap();
    server.set_event_handler(Handler);

    // Run the p2p server
    task::spawn(server.run());

    // Periodically print the node status.
    let client_clone = client.clone();
    thread::spawn(move || get_node_status(client_clone));

    let name = args.clone().name.clone();
    let name_clone = args.clone().name.clone();
    // Periodically send a request to one of the known peers.
    let client_clone = client.clone();
    thread::spawn(move || request(client_clone, &name));

    // Periodically make a broadcast to the network.
    broadcast(client, &name_clone);
}

#[derive(Debug)]
struct Handler;

impl EventHandler for Handler {
    fn handle_inbound_request(&self, request: Vec<u8>) -> Result<Vec<u8>, quantsp2p::P2pError> {
        info!(
            "📣 <<<< Inbound request: {:?}",
            String::from_utf8_lossy(request.as_slice())
        );
        Ok(request)
    }

    fn handle_broadcast(&self, topic: &str, message: Vec<u8>) {
        log::error!(
            "📣 <<<< Inbound broadcast: topic=[{:?}] broadcast=[{:?}]",
            topic,
            String::from_utf8_lossy(message.as_slice())
        );
    }
}

fn get_node_status(client: Client) {
    let dur = Duration::from_secs(10);
    loop {
        thread::sleep(dur);
        let node_status = client.get_node_status();
        error!(
            "\n\n📣 Node status: local_peer_id= {},listened_addresses= {:?},known_peers_count= {}\n\n",
            node_status.local_peer_id,
            node_status.listened_addresses,
            node_status.known_peers_count
        );
    }
}

fn request(client: Client, name: &str) {
    let dur = Duration::from_secs(60);
    let mut count = 1;
    loop {
        thread::sleep(dur);
        let known_peers = client.get_known_peers();
        if known_peers.len() > 0 {
            let target = &known_peers[0];
            let request = format!("Hello, Request {}-{count}!", name);
            info!("📣 >>>> Outbound request: {:?}", request);
            let response = client.blocking_request(target, request.as_bytes().to_vec());
            if let Ok(msg) = response {
                log::error!(
                    "📣 <<<< Inbound response: {:?}",
                    String::from_utf8_lossy(&msg)
                );
            }
            count += 1;
        }
    }
}

fn broadcast(client: Client, name: &str) {
    let dur = Duration::from_secs(30);
    let mut count = 1;

    loop {
        thread::sleep(dur);
        let topic = "block";
        let message = format!("Broadcast {}-{count}!", name);
        info!("📣 >>>> Outbound broadcast: {:?} {:?}", topic, message);
        let _ = client.broadcast(topic, message.as_bytes().to_vec());
        count += 1;
    }
}
